/* Copyright (c) 2012, Roberto Javier Godoy.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Please contact Roberto Javier Godoy at <rjgodoy@fich.unl.edu.ar>,
 * <mail@rjgodoy.com.ar> if you need additional information or have any
 * questions.
 */
package ar.com.rjgodoy.sgml.sax;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import java.util.List;
import java.util.LinkedList;

import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.DTDHandler;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.XMLReader;
import org.xml.sax.ext.DeclHandler;
import org.xml.sax.ext.LexicalHandler;

/**
 * Parser for reading an SGML document using SAX callbacks.
 *
 * <P> {@link ContentHandler#startElement(String, String, String, org.xml.sax.Attributes) ContentHandler.startElement}
 * receives an instance of {@link AttributesSgml},  which augments the per-attribute information provided through
 * the standard interface {@link Attributes}.
 *
 * <P> Namespace processing is not performed, therefore {@code uri} and {@code localName} are empty strings.
 *
 * <P> Boundaries of marked sections are notified through
 * {@link SGMLLexicalHandler#startMarkedSection(MarkedSectionStatus, boolean) startMarkedSection} and
 * {@link SGMLLexicalHandler#endMarkedSection(MarkedSectionStatus) endMarkedSection} {@code SGMLLexicalHandler}'s events.
 * Boundaries of CDATA marked sections are also notified through {@link LexicalHandler#startCDATA() startCDATA} and
 * {@link LexicalHandler#endCDATA() endCDATA} {@code LexicalHandler}'s events.
 *
 * <P>When parsing a <I>processing instruction</I>[<A href="http://xml.coverpages.org/sgmlsyn/sgmlsyn.htm#P44">44</A>],
 * the {@link ContentHandler#processingInstruction(String, String) processingInstruction} event is invoked with {@code target=null},
 * and the {@code data} parameter will contain the <I>system data</I>[<A href="http://xml.coverpages.org/sgmlsyn/sgmlsyn.htm#P45">45</A>].
 *
 * <P> The following events are not generated:
 * <UL>
 * <LI> {@link ContentHandler#startPrefixMapping(String, String) ContentHandler.startPrefixMapping}
 * <LI> {@link ContentHandler#endPrefixMapping(String) ContentHandler.endPrefixMapping}
 * <LI> {@link ContentHandler#ignorableWhitespace(char[], int, int) ContentHandler.ignorableWhitespace}
 * <LI> {@link ContentHandler#skippedEntity(String) ContentHandler.skippedEntity}
 * <LI> {@link DTDHandler#notationDecl(String, String, String) DTDHandler.notationDecl}
 * <LI> {@link DTDHandler#unparsedEntityDecl(String, String, String, String) DTDHandler.unparsedEntityDecl}
 * <LI> {@link DeclHandler} events.
 * </UL>
 *
 * <P> The following events are not generated, but might be implemented in a future version:
 * <UL>
 * <LI> {@link LexicalHandler#startEntity(String) LexicalHandler.startEntity}
 * <LI> {@link LexicalHandler#endEntity(String) LexicalHandler.endEntity}
 * <LI> Reference to an internal sdata entity in content.
 * <LI> Reference to an external data entity.
 * <LI> Reference to a subdoc entity.
 * </UL>
 *
 * <P style="color:red;font-weight:bold">WARNING: THIS API IS EXPERIMENTAL AND SUBJECT TO CHANGE</P>
 *
 * @author Roberto Javier Godoy
 *
 * @see AttributesSgml
 * @see AppinfoHandler
 * @see SGMLLexicalHandler
 */

public class SGMLReader implements XMLReader {

	private final static String SAX_FEATURES  = "http://xml.org/sax/features/";
	private final static String PROPERTIES_NS = "tag:rjgodoy.com.ar,2011:sasgml:properties:";
	private final static String FEATURES_NS   = "tag:rjgodoy.com.ar,2011:sasgml:features:";

	/**Property name: the system identifier of the SGML Declaration in effect.<BR>
	 * <B>Type:</B>    <code>String</code>.<BR>
	 * <B>Access:</B>  (parsing) read-only, (not parsing) read/write.*/
	public final static String SGMLDECL = PROPERTIES_NS+"sgmldecl";

	/**Property name: handler used to see some syntax events that are essential in some applications:
	 * marked section boundaries, characters in an ignored marked section, and the end of prolog.<BR>
	 * <B>Type:</B>    <code>{@link SGMLLexicalHandler}</code>.<BR>
	 * <B>Access:</B>  read/write.*/
	public final static String SGML_LEXICAL_HANDLER = PROPERTIES_NS+"sgmllexicalhandler";


	/**Property name: handler used to see some syntax events that are essential in some applications:
	 * comments, CDATA marked section boundaries, and the start and end of the DTD.<BR>
	 * <B>Type:</B>    <code>{@link LexicalHandler}</code>.<BR>
	 * <B>Access:</B>  read/write.*/
	public final static String LEXICAL_HANDLER = "http://xml.org/sax/properties/lexical-handler";

	/**Property name: handler used to process the APPINFO section of the SGML declaration.<BR>
	 * <B>Type:</B>    <code>{@link AppinfoHandler}</code>.<BR>
	 * <B>Access:</B>  read/write.*/
	public final static String APPINFO_HANDLER= PROPERTIES_NS+"appinfohandler";


	//HANDLERS (fields)
	private ContentHandler     content_handler;
	private DTDHandler         dtdHandler;
	private EntityResolver     entityResolver;
	private ErrorHandler       error_handler;
	private AppinfoHandler     appinfo_handler;
	private SGMLLexicalHandler sgml_lexical_handler;
	private LexicalHandler     lexical_handler;

	//SGML declaration in effect
	private String sgmldecl;

	//native pointer
	private long ptr;

	//HANDLERS (getter and setters)
	@Override
	public ContentHandler getContentHandler() {
		return content_handler;
	}

	@Override
	public DTDHandler getDTDHandler() {
		return dtdHandler;
	}

	@Override
	public EntityResolver getEntityResolver() {
		return entityResolver;
	}

	@Override
	public ErrorHandler getErrorHandler() {
		return error_handler ;
	}

	@Override
	public void setContentHandler(ContentHandler handler) {
		this.content_handler=handler;
		updateHandlers();
	}

	@Override
	public void setDTDHandler(DTDHandler handler) {
		this.dtdHandler=handler;
		updateHandlers();
	}

	@Override
	public void setEntityResolver(EntityResolver resolver) {
		this.entityResolver=resolver;
		updateHandlers();
	}

	@Override
	public void setErrorHandler(ErrorHandler handler) {
		this.error_handler=handler;
		updateHandlers();
	}

	@Override
	public void parse(InputSource input) throws SAXException {
		File tmp = null;
		InputStream  in = null;
		OutputStream out = null;
		try {
			tmp = File.createTempFile("sgml", ".tmp");
			in  = input.getByteStream();
			out = new FileOutputStream(tmp);
			byte buffer[] = new byte[65536];

			while (true) {
				int len = in.read(buffer);
				if (len<0) break;
				out.write(buffer,0,len);
			}

			in.close();  in=null;
			out.close(); out=null;
			parse(tmp.getAbsolutePath());
		}
		catch (SAXException e) {throw e;}
		catch (Exception e) {throw new SAXException(e);}
		finally {
			if (in!=null)  try{in.close();}  catch (IOException e) {}
			if (out!=null) try{out.close();} catch (IOException e) {}
			if (tmp!=null&&!tmp.delete()) tmp.deleteOnExit();
		}
	}

	@Override
	public void parse(String systemId) throws SAXException, IOException {
		if (systemId==null) throw new NullPointerException();
		synchronized(this) {
			try {parse0(sgmldecl,systemId);}
			finally {ptr=0;}
		}
	}

	@Override
	public boolean getFeature(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
		/*if (name.startsWith(SAX_FEATURES)) {
			name = name.substring(SAX_FEATURES.length());
			if (name.equals("namespaces")) return false;
			if (name.equals("namespace-prefixes")) return true;
			if (name.equals("lexical-handler/parameter-entities")) return false;
			if (name.equals("use-attributes2")) return false;
			if (name.equals("use-locator2")) return false;
			if (name.equals("use-entity-resolver2")) return false;
			if (name.equals("validation")) return true;
			throw new SAXNotRecognizedException(SAX_FEATURES+name);
		}*/
		throw new SAXNotRecognizedException(name);
	}

	@Override
	public void setFeature(String name, boolean value) throws SAXNotRecognizedException, SAXNotSupportedException {
		throw new SAXNotRecognizedException(name);
	}

	@Override
	public Object getProperty(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
		if (name.equals(SGMLDECL))             return sgmldecl;
		if (name.equals(LEXICAL_HANDLER))      return lexical_handler;
		if (name.equals(SGML_LEXICAL_HANDLER)) return sgml_lexical_handler;
		if (name.equals(APPINFO_HANDLER))      return appinfo_handler;
		throw new SAXNotRecognizedException(name);
	}

	@Override
	public void setProperty(String name, Object value) throws SAXNotRecognizedException, SAXNotSupportedException {
		if (name.equals(SGMLDECL))             {
			if (ptr!=0) throw new SAXNotSupportedException(name);
			sgmldecl=(String)value;
			return;
		}
		if (name.equals(LEXICAL_HANDLER))      {lexical_handler=(LexicalHandler)value; updateHandlers(); return;}
		if (name.equals(SGML_LEXICAL_HANDLER)) {sgml_lexical_handler=(SGMLLexicalHandler)value; updateHandlers(); return;}
		if (name.equals(APPINFO_HANDLER))      {appinfo_handler=(AppinfoHandler)value; updateHandlers(); return;}
		throw new SAXNotRecognizedException(name);
	}


	private AppinfoHandler getAppinfoHandler() {
		return appinfo_handler;
	}

	private SGMLLexicalHandler getSgmlLexicalHandler() {
		return sgml_lexical_handler;
	}

	private LexicalHandler getLexicalHandler() {
		return lexical_handler;
	}


	private void updateHandlers() {
		if (ptr!=0) updateHandlers0(ptr);
	}

	private native void parse0(String sgmldecl, String systemId);
	private native void updateHandlers0(long ptr);
	private native static void init0();

	private static String tryPreload(String library, String arch, List<Error> errors) {
		//try preloading library_arch.dll
		if (arch!=null&&tryPreload(library+"_"+arch,null,errors)!=null) return library+"_"+arch;
		
		//try preloading library.dll
		try {			
			System.loadLibrary(library);
			return library;
		} catch (UnsatisfiedLinkError e) {			
			//library will be resolved when loading sasgml			
			errors.add(e);
			return null;
		}
	}
	
	static {
		List<Error> errors = new LinkedList<Error>();
		String arch = System.getProperty("sun.arch.data.model");		
		
		//load osp152_<arch>.dll or osp152.dll
		String osp = tryPreload("osp152",arch,errors);
		
		//load either sasgml_<arch>.dll or sasgml.dll		
		String sasgml = tryPreload("sasgml",arch,errors);			
		
		if (sasgml==null) {
			for (Error e:errors) e.printStackTrace();
			osp= (osp==null)?"osp not preloaded;":("osp="+osp+";");
			System.err.println("arch="+arch+";"+osp);			
		}
		
		init0();
	}


}
